//
//  ZLVideoDecoder.m
//  Video
//
//  Created by os on 2021/3/30.
//

#import "ZLVideoDecoder.h"
#import <AVFoundation/AVFoundation.h>
#import <VideoToolbox/VideoToolbox.h>

#import "ZLAVConfig.h"

@interface ZLVideoDecoder ()

@property (nonatomic, strong) dispatch_queue_t decodeQueue;
@property (nonatomic, strong) dispatch_queue_t callbackQueue;

// 解码会话
@property (nonatomic) VTDecompressionSessionRef decodeSession;

@end

@implementation ZLVideoDecoder {
    uint8_t *_sps;
    NSUInteger _spsSize;
    uint8_t *_pps;
    NSUInteger _ppsSize;
    CMVideoFormatDescriptionRef _decodeDesc;
}

/// 解码回调函数
void videoDecompressionOutputCallback(void * CM_NULLABLE decompressionOutputRefCon,
                                      void * CM_NULLABLE sourceFrameRefCon,
                                      OSStatus status,
                                      VTDecodeInfoFlags infoFlags,
                                      CM_NULLABLE CVImageBufferRef imageBuffer,
                                      CMTime presentationTimeStamp,
                                      CMTime presentationDuration) {
    
    if (status != noErr) {
        NSLog(@"Video hard decode callback error status = %d", status);
        return;
    }
    
    // 解码后的数据sourceFrameRefCon -> CVPixelBufferRef
    CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon;
    *outputPixelBuffer = CVPixelBufferRetain(imageBuffer);
    
    // 获取 self
    ZLVideoDecoder *decoder = (__bridge ZLVideoDecoder *)(decompressionOutputRefCon);
    
    // 调用回调队列
    dispatch_async(decoder.callbackQueue, ^{
        
        // 将解码后的数据给 decoder 代理，
        [decoder.delegate videoDecodeCallback:imageBuffer];
        // 释放数据
        CVPixelBufferRelease(imageBuffer);
        
    });
    
}

- (instancetype)initWithConfig:(ZLVideoConfig *)config {
    if (self = [super init]) {
        // 初始化 config 信息
        _config = config;
        // 创建解码队列与回调队列
        _decodeQueue = dispatch_queue_create("h264 hard decode queue", DISPATCH_QUEUE_SERIAL);
        _callbackQueue = dispatch_queue_create("h264 hard decode callback queue", DISPATCH_QUEUE_SERIAL);
    }
    return self;
}

/// 初始化解码器
- (BOOL)initDecoder {
    
    if (_decodeSession) {
        return true;
    }
    const uint8_t * const parameterSetPoints[2] = {_sps, _pps};
    const size_t parametersSetSizes[2] = {_spsSize, _ppsSize};
    int naluHeaderLen = 4;
    
    /**
     根据 sps pps 设置解码参数
     参数1 kCFAllocatorDefault 分配器
     参数2 2 参数个数
     参数3 parameterSetPoints 参数集指针
     参数4 parameterSetSizes 参数集大小
     参数5 naluHeaderLen startCode的长度
     参数6 _decodeDesc 解码器描述
    */
    OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2, parameterSetPoints, parametersSetSizes, naluHeaderLen, &_decodeDesc);
    if (status != noErr) {
        NSLog(@"Video hard DecodeSession create H264ParameterSets(sps, pps) failed status = %d", status);
        return NO;
    }
    
    /**
     解码参数:
     kCVPixelBufferPixelFormatTypeKey : 摄像头的输出数据格式
        kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange，即420v
        kCVPixelFormatType_420YpCbCr8BiPlanarFullRange，即420f
        kCVPixelFormatType_32BGRA，iOS在内部进行YUV至BGRA格式转换
     YUV420一般用于标清视频，YUV422用于高清视频，这里的限制让人感到意外。但是，在相同条件下，YUV420计算耗时和传输压力比YUV422都小。
     
     kCVPixelBufferWidthKey/kCVPixelBufferHeightKey: 视频源的分辨率 width*height
     kCVPixelBufferOpenGLCompatibilityKey : 它允许在 OpenGL 的上下文中直接绘制解码后的图像，而不是从总线和 CPU 之间复制数据。这有时候被称为零拷贝通道，因为在绘制过程中没有解码的图像被拷贝.
     
    */
    NSDictionary *destinationPixBufferAttrs = @{
        (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange),
        (id)kCVPixelBufferWidthKey : @(_config.width),
        (id)kCVPixelBufferHeightKey : @(_config.height),
        (id)kCVPixelBufferOpenGLCompatibilityKey : @(YES)
    };
    
    /**
     @abstract 解码回调设置
     @discussion VTDecompressionOutputCallbackRecord 是一个简单的结构体，它带有一个指针 (decompressionOutputCallback)，指向帧解压完成后的回调方法。你需要提供可以找到这个回调方法的实例 (decompressionOutputRefCon)。VTDecompressionOutputCallback 回调方法包括七个参数：
     param decompressionOutputRefCon 回调的引用
     param sourceFrameRefCon 帧的引用
     param status 一个状态标识 (包含未定义的代码)
     param infoFlags 指示同步/异步解码，或者解码器是否打算丢帧的标识
     param imageBuffer 实际图像的缓冲
     param 出现的时间戳
     param 出现的持续时间
     */
    VTDecompressionOutputCallbackRecord callbackRecord;
    callbackRecord.decompressionOutputCallback = videoDecompressionOutputCallback;
    callbackRecord.decompressionOutputRefCon = (__bridge void * _Nullable)self;
    
    /**
     创建 session
     @abstract    创建用于解压缩视频帧的会话。
     @discussion  解压后的帧将通过调用OutputCallback发出
     @param    allocator  内存的会话。通过使用默认的kCFAllocatorDefault的分配器。
     @param    videoFormatDescription 描述源视频帧
     @param    videoDecoderSpecification 指定必须使用的特定视频解码器.NULL
     @param    destinationImageBufferAttributes 描述源像素缓冲区的要求 NULL
     @param    outputCallback 使用已解压缩的帧调用的回调
     @param    decompressionSessionOut 指向一个变量以接收新的解压会话
     */
    status = VTDecompressionSessionCreate(kCFAllocatorDefault, _decodeDesc, NULL, (__bridge CFDictionaryRef _Nullable)destinationPixBufferAttrs, &callbackRecord, &_decodeSession);
    if (status != noErr) {
        NSLog(@"Video hard DecodeSession create failed status = %d", status);
        return NO;
    }
    
    // 设置解码会话属性（实时编码）
    status = VTSessionSetProperty(_decodeSession, kVTDecompressionPropertyKey_RealTime, kCFBooleanTrue);
    NSLog(@"Vidoe hard decodeSession set property RealTime status = %d", status);
    
    return YES;
}

/// 解码函数
- (CVPixelBufferRef)decode:(uint8_t *)frame withSize:(uint32_t)frameSize {
    CVPixelBufferRef outputPixelBuffer = NULL;
    CMBlockBufferRef blockBuffer = NULL;
    CMBlockBufferFlags flag0 = 0;
    
    /**
     创建blockBuffer
     参数1: structureAllocator kCFAllocatorDefault
     参数2: memoryBlock  frame
     参数3: frame size
     参数4: blockAllocator: Pass NULL
     参数5: customBlockSource Pass NULL
     参数6: offsetToData  数据偏移
     参数7: dataLength 数据长度
     参数8: flags 功能和控制标志
     参数9: newBBufOut blockBuffer地址,不能为空
     */
    OSStatus status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, frame, frameSize, kCFAllocatorNull, NULL, 0, frameSize, flag0, &blockBuffer);
    if (status != kCMBlockBufferNoErr) {
        NSLog(@"Video hard decode create blockBuffer error code = %d", status);
        return outputPixelBuffer;
    }
    
    CMSampleBufferRef sampleBuffer = NULL;
    const size_t sampleSizeArray[] = {frameSize};
    
    /**
     创建 sampleBuffer
     参数1: allocator 分配器,使用默认内存分配, kCFAllocatorDefault
     参数2: blockBuffer.需要编码的数据blockBuffer.不能为NULL
     参数3: formatDescription,视频输出格式
     参数4: numSamples.CMSampleBuffer 个数.
     参数5: numSampleTimingEntries 必须为0,1,numSamples
     参数6: sampleTimingArray.  数组.为空
     参数7: numSampleSizeEntries 默认为1
     参数8: sampleSizeArray
     参数9: sampleBuffer对象
     */
    status = CMSampleBufferCreateReady(kCFAllocatorDefault, blockBuffer, _decodeDesc, 1, 0, NULL, 1, sampleSizeArray, &sampleBuffer);
    if (status != noErr || !sampleBuffer) {
        NSLog(@"Video hard decode create sampleBuffer failed status = %d", status);
        CFRelease(blockBuffer);
        return outputPixelBuffer;
    }
    
    // 解码 向视频解码器提示使用低功耗模式是可以的
    VTDecodeFrameFlags flag1 = kVTDecodeFrame_1xRealTimePlayback;
    // 异步解码
    VTDecodeInfoFlags flag2 = kVTDecodeInfo_Asynchronous;
    
    /**
     解码数据
     参数1: 解码session
     参数2: 源数据 包含一个或多个视频帧的CMsampleBuffer
     参数3: 解码标志
     参数4: 解码后数据outputPixelBuffer
     参数5: 同步/异步解码标识
     */
    status = VTDecompressionSessionDecodeFrame(_decodeSession, sampleBuffer, flag1, &outputPixelBuffer, &flag2);
    if (status == kVTInvalidSessionErr) {
        NSLog(@"Video hard decode InvalidSessionErr status = %d", status);
    } else if (status == kVTVideoDecoderBadDataErr) {
        NSLog(@"Video hard decode BadData status = %d", status);
    } else if (status != noErr) {
        NSLog(@"Video hard decode failed status = %d", status);
    }
    CFRelease(sampleBuffer);
    CFRelease(blockBuffer);
    
    return outputPixelBuffer;
}

- (void)decodeNaluData:(uint8_t *)frame size:(uint32_t)size {
    // 数据类型:frame的前4个字节是NALU数据的开始码，也就是00 00 00 01，
    // 第5个字节是表示数据类型，转为10进制后，7是sps, 8是pps, 5是IDR（I帧）信息
    int type = {frame[4] & 0x1F};
    
    // 将NALU的开始码转为4字节大端NALU的长度信息
    uint32_t naluSize = size - 4;
    uint8_t *pNaluSize = (uint8_t *)(&naluSize);
    CVPixelBufferRef pixelBuffer = NULL;
    frame[0] = *(pNaluSize + 3);
    frame[1] = *(pNaluSize + 2);
    frame[2] = *(pNaluSize + 1);
    frame[3] = *(pNaluSize);
    
    /**
     第一次解析时: 初始化解码器initDecoder
     */
    switch (type) {
        case 0x05: //关键帧
            if ([self initDecoder]) {
                pixelBuffer = [self decode:frame withSize:size];
            }
            break;
        case 0x06: // SEI 增强信息
            NSLog(@"SEI");//增强信息
            break;
        case 0x07: // sps
            _spsSize = naluSize;
            _sps = malloc(_spsSize);
            memcpy(_sps, &frame[4], _spsSize);
            break;
        case 0x08: // pps
            _ppsSize = naluSize;
            _pps = malloc(_ppsSize);
            memcpy(_pps, &frame[4], _ppsSize);
            break;
        default: // 其他帧
            if ([self initDecoder]) {
                pixelBuffer = [self decode:frame withSize:size];
            }
            break;
    }
}

- (void)decodeNaluData:(NSData *)frame {
    // 将解码放在异步队列
    dispatch_async(_decodeQueue, ^{
        // 获取 frame 二进制数据
        uint8_t *nalu = (uint8_t *)frame.bytes;
        // 调用解码 nalu 数据方法
        [self decodeNaluData:nalu size:(uint32_t)frame.length];
    });
}

- (void)dealloc {
    if (_decodeSession) {
        VTDecompressionSessionInvalidate(_decodeSession);
        CFRelease(_decodeSession);
        _decodeSession = nil;
    }
}

@end
